package agendaSystem;


import java.util.*;

import be.kuleuven.cs.som.annotate.*; 
import agendaSystem.Agenda;


/**
 * A class for the creation of entries based on the day, description and number of agendas.
 * 
 * @invar	The description of each entry must be a valid description for an entry. 
 * 			| isValidDecription(getDescription)
 * @invar	The day of each entry must be a valid day for an entry.
 * 			| isValidDay(getDay)
 * 
 * @author	Sabrina Knapen, Axel Vranken
 * @version 1.0 
 * 
 */
public abstract class Entry {

	/**
	 * Initialize a new Entry with a given description, day, agendas.
	 * 
	 * @param	description
	 * 			The description of the new entry
	 * @param	day
	 * 			The day of the new entry
	 * @param	agendas
	 * 			The agenda that shall contain the new entry
	 * @pre		The description must be valid for the new entry. 
	 * 			| isValidDescription(description)
	 * @pre		The given day must be a valid day for the entry. 
	 * 			| isValidDay(day)
	 * @pre		The given agendas must exist 
	 * 			| for each Agenda ag in agendas
	 * 			| 		ag.isTerminated = false
	 * @post	The description of this new entry is equal to the given
	 * 			description. 
	 * 			| new.getDescription() == description
	 * @post	The day of this new entry is equal to the given day. 
	 * 			| new.getDay() == day
	 * @post	The agendas of this new entry is the same as the
	 *          given agendas.
	 *       	| new.getAgenda().contains(agendas)
	 * @post	The given agendas have this new entry as one of the
	 *         	entries on the given day.
	 *         	| for each ag in agendas
	 *       	| 		ag.getEntriesAt(day) = List <Entry> entries.contains(this)
	 * @post    The number of entries for the given agendas on the given day is
	 *          incremented by 1.
	 *          | for each ag in agendas
	 *       	| 		ag.getAllEntries() == ag.getAllEntries() + 1
	 * @effect	The entry belongs to an array of agendas
	 * 			| for each ag in agendas
	 * 			| 		ag.addEntry(this)
	 * @effect	The agendas are added to the list of agendas for this entry.
	 * 			| for each ag in agenda
	 * 			| 		this.addAgenda(ag)
	 * @throws	IllegalArgumentException
	 * 			Minimum one agenda must be given.
	 * 			|( agendas.length != 0)
	 */
	public Entry(String description, int day, Agenda... agendas)throws IllegalArgumentException{	
		if( agendas.length == 0)
			throw new IllegalArgumentException("At least one agenda must have this entry");
		setDescription(description);
		setDay(day);
		for(Agenda ag:agendas){
		ag.addEntry(this);
		}
	}
	
	/**
	 * Initialize a new Entry with a given description, day.
	 * 
	 * @param	description
	 * 			The description of the new entry
	 * @param	day
	 * 			The day of the new entry
	 * @pre		The description must be valid for the new entry. 
	 * 			| isValidDescription(description)
	 * @pre		The given day must be a valid day for the entry. 
	 * 			| isValidDay(day)
	 * @post	The description of this new entry is equal to the given
	 * 			description. 
	 * 			| new.getDescription() == description
	 * @post	The day of this new entry is equal to the given day. 
	 * 			| new.getDay() == day
	 * 
	 */
	public Entry(String description, int day){	
		setDescription(description);
		setDay(day);
	}
		
	/**
	 * 
	 * @return	Returns the day of the current entry. 
	 */	 
	@Basic
	public int getDay() {
		return this.day;
	}

	/**
	 * 
	 * @return	Returns the description attached to the entry. 
	 */
	@Basic
	public String getDescription() {
		return this.description;
	}

	/**
	 * @return Returns a list containing agendas for a specific entry. 
	 */
	public List <Agenda> getAgenda(){
		return this.agenda; 
	}
	
	/**
	 * Add the agenda of this entry to the list of agendas.
	 * 
	 * @param  agenda
	 *         The new agenda for this entry.
	 * @post   The list of agendas for this entry contains the
	 *         given agenda.
	 *         | new.getAgenda().contains(agenda)
	 * @throws IllegalArgumentException
	 *         This entry cannot have the given agenda as its agenda.
	 *         | ! canHaveAsAgenda(agenda)
	 */
	@Raw 
	protected void addAgenda(Agenda agenda) throws IllegalArgumentException {
		if (! canHaveAsAgenda(agenda))
			throw new IllegalArgumentException("Inappropriate Agenda!");
		else
		this.agenda.add(agenda);	
	}
	
	/**
	 * Removes the agenda of this entry from the list of agendas.
	 * 
	 * @param  agenda
	 *         The agenda of this entry.
	 * @post   The list of agendas for this entry does not contain the
	 *         given agenda anymore.
	 *       | new.getAgenda().remove(agenda)
	 * @throws IllegalArgumentException
	 *         This entry does not have the given agenda as its agenda.
	 *       | ! this.getAgenda().contains(agenda)
	 */
	@Raw 
	protected void removeAgenda(Agenda agenda) throws IllegalArgumentException {
		if (this.agenda.contains(agenda)){
			this.agenda.remove(agenda);	
		}
		else{
			throw new IllegalArgumentException("The agenda cannnot be removed for this entry, the given entry does not contain this agenda");
		}
	}
	
	/**
	 * Check whether this entry can have the given agenda as
	 * its agenda.
	 * 
	 * @param  agenda
	 * 		   The agenda to check.
	 * @return If this entry is terminated, true if and only if the
	 *         given agenda is not effective.
	 *         | if (this.isTerminated())
	 *         |   then result == (agenda == null)
	 * @return If this entry is not terminated, true if and only if the given
	 *         agenda is effective and not yet terminated.
	 *         | if (! this.isTerminated())
	 *         |   then result ==
	 *         |     (agenda != null) &&
	 *         |     (! agenda.isTerminated())
	 */
	public boolean canHaveAsAgenda(Agenda agenda){ 
		if(this.isTerminated())
			return agenda == null;
		return (agenda != null) && (!agenda.isTerminated());
	}
	
	/**
	 * Terminate this entry.
	 * 
	 * @post   	This entry is terminated.
	 *       	| new.isTerminated()
	 * @post   	This entry no longer references an effective agenda.
	 *       	| new.getAgenda() == null
	 * @post  	If this entry was not yet terminated, this entry
	 *         	is no longer one of the entries for the agenda to which
	 *         	this entry belonged.
	 *       	| if (! isTerminated())
	 *       	|   then (! new getAgenda()).getAllEntries.contains(this))
	 * @post   	If this entry was not yet terminated, the number of
	 *        	 entries in the list of entries on the day of the entry is
	 *        	 decremented by 1.
	 *       	| if (! isTerminated())
	 *      	|   then (new getDay()).getAllEntries().size ==
	 *      	|            getDay().getAllEntries().size - 1
	 * @post   	If this entry was not yet terminated, the day
	 *         	involved in this list of entries no longer has this entry as
	 *         	one of its entries.
	 *      	| if (! isTerminated())
	 *       	|   then (! getEntriesAt(getDay().contains(entry))
	 * @post   	This entry no longer has any slots.
	 *       	| new.getSlots().length == 0
	 */
	public void terminate() { 
		if (!isTerminated()) {
			if (!this.getAgenda().isEmpty()) {
				List<Agenda> agendaEntry = new ArrayList<Agenda>();
				agendaEntry = this.getAgenda();
				for (Agenda ag : agendaEntry)
					ag.removeEntry(this);
				boolean[] slotsEntry = this.getSlots();
				for (int i = 0; i < slotsEntry.length; i++) {
					slotsEntry[i] = false;
				}
				this.isTerminated = true;
			}
			else {
				boolean[] slotsEntry = this.getSlots();
				for (int i = 0; i < slotsEntry.length; i++) {
					slotsEntry[i] = false;
				}
				this.isTerminated = true;
			}
		}
	}

	/**
	 * Check whether this entry is already terminated.
	 */
	protected boolean isTerminated() {
		return this.isTerminated;
	}

	/**
	 * Set the description of the entry to the given description.
	 * 
	 * @param	description
	 * 			The new description of the entry.
	 * @pre		The given description must be a valid description for the entry.
	 * 			| isValidDescription(description)
	 * @post	The given description is equal to the new description of the entry.
	 * 			| new.getDescription() == description
	 */
	public void setDescription(String description) {
		assert isValidDescription(description);
		this.description = description; 
	}

	/**
	 * Set the day of the entry to the given day.
	 * 
	 * @param	day
	 * 			The new day of the entry
	 * @pre		The given day must be a valid day for the entry.
	 * 			| isValidDay(day)
	 * @post	The given day is equal to the new day of the entry.
	 * 			| new.getDay() == day 
	 */
	public void setDay(int day) {
		assert isValidDay(day);
		this.day = day;		
	}

	/**
	 * Check if the given day is a valid day for an entry.
	 * 
	 * @param	day
	 * 			The day that needs to be checked.
	 * @return	True if and only if the given day is a number between 0 and the maximum number of days.
	 * 			|result == ((day >= 0) && (day <= getMaxday())
	 */
	public static boolean isValidDay(int day) {
		return (day >= 0) && (day <= getMaxDay());
	}
	
	/**
	 * Return the maximum of days possible in the agenda system.
	 */
	public static int getMaxDay() {
		return MAXDAYS;
	}
	
	/**
	 * Variable setting the maximum of days.
	 */
	private final static int MAXDAYS = 3000;
	
	
	/**
	 * Check if the given description is a valid description for an entry.
	 * 
	 * @param	description
	 * 			The description that needs to be checked.
	 * @return	True if and only if the description matches a pattern ( must
	 * 			start with a letter or one quote(')) The rest of the description
	 * 			can be of a variable number of letters, numbers, _ , ' and ". 
	 * 			| pattern = ^[a-zA-Z\'][a-zA-Z0-9_\'\" ]*$ 
	 * 			| 	result == (description.matches(pattern))
	 */
	public static boolean isValidDescription(String description) { 
		String pattern = ("^[a-zA-Z\'][a-zA-Z0-9_\'\" ]*$");
		return description.matches(pattern);
	}

	
	/**
	 * Move an entry a given number of days.
	 * 
	 * @param	day
	 * 			The number of days to move the entry.
	 * @post	When the entry is not in an agenda, the day of the entry is set to the given days 
	 * 			added to the day of the entry.
	 * 			| if (agendasOfEntry.isEmpty())
	 * 			| 		then new.day = this.getDay() + days
	 * 			|			 this.setDay(day)
	 * @post	When the entry is in an agenda, for each agenda of the entry, the entry is first remove, then set new day 
	 * 			to the given days added to the day of the entry and then the entry is back added to each agenda.
	 * 			| if(!agendasOfEntry.isEmpty())
	 * 			|		then for each agenda in agendasOfEntry
	 * 			|			agenda.removeEntry(this)
	 * 			|			new.day = this.getDay() + days
	 * 			|			this.setDay(day)
	 * 			|			agenda.add(entry)			
	 * @throws 	IllegalArgumentException
	 * 			The new day is not a valid day for the entry.
	 * 			| !isValidDay(this.getday() + days)
	 * 
	 * 
	 */
	public void moveNbDays(int days) throws IllegalArgumentException {
		Integer d = this.getDay();
		List<Agenda> agendasOfEntry = this.getAgenda();
		List<Agenda> agendasOfEntryCopy = new ArrayList<Agenda>(agendasOfEntry);
		if (!agendasOfEntryCopy.isEmpty()) {
			for (Agenda ag : agendasOfEntryCopy) {
				List<Entry> allEntriesInAgenda = ag.getEntriesAt(d);
				List<Entry> allEntriesInAgendaCopy = new ArrayList<Entry>(
						allEntriesInAgenda);
				for (Entry en : allEntriesInAgendaCopy) {
					if (en == this) {
						ag.removeEntry(this);
						this.addAgenda(ag);
					}
				}
			}
			this.setDay(this.getDay() + days);
			List<Agenda> agendas = this.getAgenda();
			List<Agenda> agendasCopy = new ArrayList<Agenda>(agendas);
			for (Agenda ag : agendasCopy) {
				ag.addEntry(this);
				this.removeAgenda(ag);
			}
		} else {
			this.setDay(this.getDay() + days);
		}
	}
	
	/**
	 * Checks whether the entry overlaps with the other entry.
	 * @param other
	 * 		  The other entry to compare with.
	 *
	 */
	public abstract boolean overlapsWith(Entry other);
	
	/**
	 * 
	 * Return the slots attached to the entry. The slots are the hours
	 * 			scheduled for the entry.
	 */
	@Basic 
	public abstract boolean[] getSlots();
	
	/**
	 * A method for getting the first slot of a given entry.	
	 * 
	 */
	public abstract int getFirstSlot() ;
	
	/**
	 * A method for getting the last slot of a given entry.	
	 * 
	 */
	public abstract int getLastSlot() ;
	
	/**
	 * A method for getting the number of slots of an entry.
	 * 
	 */
	public abstract int getNbSlots();
	
	/**
	 * Variable registering the description of an entry.
	 */
	private String description;
	
	/**
	 * Variable registering the day of an entry.
	 */
	private int day;
	
	/**
	 * List to register all the agendas for an Entry.
	 */
	private List<Agenda> agenda = new ArrayList<Agenda>();
	
	/**
     * Variable registering whether or not this entry has been
     * terminated.
     */
    boolean isTerminated = false;

	}
	
